﻿// F# の詳細 (http://fsharp.net)

module MySQLQueryMonitorModule

open System
open System.IO
open System.Text
open PcapDotNet.Core
open PcapDotNet.Packets

let parse3BytesInt (bytes : byte array) =
    let dwordBytes = Array.create 4 0uy
    array.Copy(bytes, dwordBytes, bytes.Length)
    BitConverter.ToInt32(dwordBytes, 0)

let getTcpPayload (packet : Packet) =
    let stream = packet.Ethernet.IpV4.Tcp.Payload.ToMemoryStream()
    new BinaryReader(stream)

type MySQLCommand =
    | COM_SLEEP | COM_QUIT | COM_INIT_DB | COM_QUERY of string
    | COM_FIELD_LIST | COM_CREATE_DB | COM_DROP_DB | COM_REFRESH
    | COM_SHUTDOWN | COM_STATISTICS
    | COM_PROCESS_INFO | COM_CONNECT | COM_PROCESS_KILL
    | COM_DEBUG | COM_PING | COM_TIME | COM_UNKNOWN

type MySQLResult =
    | OK_Result
    | ERR_Result
    | EOF_Result
    | Unknown_Result

let parseMySQLPacketHeader (reader : BinaryReader) =
    let length    = reader.ReadBytes(3) |> parse3BytesInt
    let packetNum = reader.ReadInt32()
    let indicator = reader.ReadByte()
    let payload   = reader.ReadBytes(length)
    (length, packetNum, indicator, payload)

let parseMySQLCommandPacket packet =
    use reader = getTcpPayload(packet)
    let (length, packetNum, indicator, payload) = parseMySQLPacketHeader reader
    match indicator with
    | 0uy  -> COM_SLEEP
    | 1uy  -> COM_QUIT
    | 2uy  -> COM_INIT_DB
    | 3uy  -> COM_QUERY (payload |> Encoding.UTF8.GetString)
    | 4uy  -> COM_FIELD_LIST
    | 5uy  -> COM_CREATE_DB
    | 6uy  -> COM_DROP_DB
    | 7uy  -> COM_REFRESH
    | 8uy  -> COM_SHUTDOWN
    | 9uy  -> COM_STATISTICS
    | 10uy -> COM_PROCESS_INFO
    | 11uy -> COM_CONNECT
    | 12uy -> COM_PROCESS_KILL
    | 13uy -> COM_DEBUG
    | 14uy -> COM_PING
    | 15uy -> COM_TIME
    | _    -> COM_UNKNOWN

let parseMySQLResultPacket packet =
    use reader = getTcpPayload(packet)
    let (length, packetNum, indicator, payload) = parseMySQLPacketHeader reader
    match indicator with
    | 0x00uy -> OK_Result
    | 0xffuy -> ERR_Result
    | 0xfeuy -> EOF_Result
    | _      -> Unknown_Result

type MySQLQueryMonitor(device : LivePacketDevice, port : uint16, timeout : int) =
    let communicator = device.Open(65536, PacketDeviceOpenAttributes.Promiscuous, timeout)
    let filter = sprintf "ip and tcp and port %d" port |> communicator.CreateFilter
    do communicator.SetFilter filter
    member this.Receive() : Packet =
        let mutable packet = null
        communicator.ReceivePacket(&packet) |> ignore
        packet
    member this.Dispose() =
        filter.Dispose()
        communicator.Dispose()
    interface IDisposable with
        member this.Dispose() = this.Dispose()